package gui;

import gui.treenodes.ParentTransactionTreeNode;
import gui.treenodes.RootTransactionTreeNode;
import gui.treenodes.TransactionNode;
import gui.treenodes.TransactionTypeNode;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import core.Transaction;

public class TransactionTreeModel implements TreeModel
{
	private RootTransactionTreeNode root;
	private Map<String, TransactionTypeNode> typeMap;
	private Collection<TreeModelListener> treeListeners;
	
	public TransactionTreeModel(String rootName){
		root = new RootTransactionTreeNode(rootName);
		typeMap = new HashMap<String, TransactionTypeNode>();
		treeListeners = new HashSet<TreeModelListener>();
	}
	
	public void loadTransactions(Collection<Transaction> transactions){
		for(Transaction transaction : transactions){
			String tranType = transaction.getType();
			TransactionTypeNode typeNode = typeMap.get(tranType);
			if(typeNode == null){
				typeNode = new TransactionTypeNode(tranType);
				typeMap.put(tranType, typeNode);
				root.add(typeNode);
			}
			typeNode.add(new TransactionNode(transaction));
		}
		fireTransactionsLoaded();
	}
	
	public void addTransaction(Transaction transaction){
		String tranType = transaction.getType();
		TransactionTypeNode typeNode = typeMap.get(tranType);
		if(typeNode == null){
			addTransactionType(tranType);
			typeNode = typeMap.get(tranType);
		}
		typeNode.add(new TransactionNode(transaction));
		fireTransactionAdded(transaction);
	}
	
	public void editTransaction(Transaction transaction, String oldType){
		if(transaction.getType().equals(oldType)){
			fireTransactionEdited(transaction);
		}
		else{
			removeTransaction(transaction, oldType);
			addTransaction(transaction);
		}
	}
	
	public void removeTransaction(Transaction transaction){
		removeTransaction(transaction, transaction.getType());
	}
	
	private void removeTransaction(Transaction transaction, String type){
		TransactionTypeNode typeNode = typeMap.get(type);
		TransactionNode node = typeNode.getNodeByTransaction(transaction);
		int index = typeNode.getIndex(node);
		typeNode.remove(node);
		fireTransactionRemoved(typeNode, node, index);
		if(typeNode.getChildCount() == 0){
			removeTransactionType(typeNode.getType());
		}
	}
	
	private void addTransactionType(String tranType){
		TransactionTypeNode typeNode = new TransactionTypeNode(tranType);
		typeMap.put(tranType, typeNode);
		root.add(typeNode);
		fireTransactionTypeAdded(tranType);
	}
	
	private void removeTransactionType(String tranType){
		TransactionTypeNode typeNode = root.getNode(tranType);
		int index = root.getIndex(typeNode);
		root.remove(typeNode);
		fireTransactionTypeRemoved(typeNode, index);
	}
	
	@Override public void addTreeModelListener(TreeModelListener l){
		treeListeners.add(l);		
	}
	
	@Override public Object getChild(Object parent, int index){
		if(!(parent instanceof ParentTransactionTreeNode)){
			throw new IllegalArgumentException(parent + " is leaf!");
		}
		else{
			Object toReturn = ((ParentTransactionTreeNode)parent).getNodeAt(index);
			return toReturn;
		}
	}

	@Override public int getChildCount(Object parent){
		if(!(parent instanceof ParentTransactionTreeNode)){
			throw new IllegalArgumentException(parent + " is leaf!");
		}
		else{
			int toReturn = ((ParentTransactionTreeNode)parent).getChildCount();
			return toReturn;
		}
	}

	@Override public int getIndexOfChild(Object parent, Object child){
		if(!(parent instanceof ParentTransactionTreeNode)){
			throw new IllegalArgumentException(parent + " is leaf!");
		}
		else{
			int toReturn = ((ParentTransactionTreeNode)parent).getIndex(child);
			return toReturn;
		}
	}
	
	@Override public Object getRoot(){
		return root;
	}
	
	@Override public boolean isLeaf(Object node){
		return !(node instanceof ParentTransactionTreeNode);
	}
	
	@Override public void removeTreeModelListener(TreeModelListener l){
		treeListeners.remove(l);
	}

	@Override public void valueForPathChanged(TreePath path, Object newValue){
		System.out.println("Currently unsupported");
	}
	
	private void fireTransactionAdded(Transaction transaction){
		fireNodesInserted(createTransactionEvent(transaction));
	}
	
	private void fireTransactionEdited(Transaction transaction){
		fireNodesEdited(createTransactionEvent(transaction));
	}
	
	private void fireTransactionRemoved(TransactionTypeNode typeNode, TransactionNode node, int index){
		Object[] path = new Object[]{root, typeNode};
		int[] childrenIndices = new int[]{index};
		Object[] children = new Object[]{node};
		TreeModelEvent event = new TreeModelEvent(this, path, childrenIndices, children);
		fireNodesRemoved(event);
	}
	
	private TreeModelEvent createTransactionEvent(Transaction transaction){
		TransactionTypeNode typeNode = typeMap.get(transaction.getType());
		TransactionNode transactionNode = typeNode.getNodeByTransaction(transaction);
		Object[] path = new Object[]{root, typeNode};
		int[] childrenIndices = new int[]{typeNode.getIndex(transactionNode)};
		Object[] children = new Object[]{transactionNode};
		return new TreeModelEvent(this, path, childrenIndices, children);
	}

	private void fireTransactionsLoaded(){
		fireStructureChanged(new TreeModelEvent(this, new Object[]{root}));
	}
	
	private void fireTransactionTypeAdded(String type){
		TransactionTypeNode typeNode = root.getNode(type);
		Object[] path = new Object[]{root};
		int[] childrenIndices = new int[]{root.getIndex(typeNode)};
		Object[] children = new Object[]{typeNode};
		TreeModelEvent event = new TreeModelEvent(this, path, childrenIndices, children);
		fireNodesInserted(event);
	}
	
	private void fireTransactionTypeRemoved(TransactionTypeNode typeNode, int oldIndex){
		Object[] path = new Object[]{root};
		int[] childrenIndices = new int[]{oldIndex};
		Object[] children = new Object[]{typeNode};
		TreeModelEvent event = new TreeModelEvent(this, path, childrenIndices, children);
		fireNodesRemoved(event);
	}

	private void fireNodesInserted(TreeModelEvent event){
		for(TreeModelListener listener : treeListeners){
			listener.treeNodesInserted(event);
		}
	}

	private void fireNodesEdited(TreeModelEvent event){
		for(TreeModelListener listener : treeListeners){
			listener.treeNodesChanged(event);
		}
	}

	private void fireNodesRemoved(TreeModelEvent event){
		for(TreeModelListener listener : treeListeners){
			listener.treeNodesRemoved(event);
		}
	}
	
	private void fireStructureChanged(TreeModelEvent event){
		for(TreeModelListener listener : treeListeners){
			listener.treeStructureChanged(event);
		}
	}
}
